package com.bing.java.design_pattern.singleton;

/**
 * Ⅳ 双重校验锁-线程安全
 * @author bing
 *
 * 2019年11月9日 下午4:09:40
 * 
 * 考虑下面的实现，也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下，如果两个线程都执行了 if 语句，那么两个线程都会进入 if 语句块内。
 * 虽然在 if 语句块内有加锁操作，但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句，只是先后的问题，那么就会进行两次实例化。
 * 因此必须使用双重校验锁，也就是需要使用两个 if 语句：第一个 if 语句用来避免 uniqueInstance 已经被实例化之后的加锁操作，而第二个 if 语句进行了加锁，
 * 所以只能有一个线程进入，就不会出现 uniqueInstance == null 时两个线程同时进行实例化操作。
 * 
 * uniqueInstance 采用 volatile 关键字修饰也是很有必要的， uniqueInstance = new Singleton(); 这段代码其实是分为三步执行：
  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性，执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题，但是在多线程环境下会导致一个线程获得还没有初始化的实例。
例如，线程 T1 执行了 1 和 3，此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空，因此返回 uniqueInstance，但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排，保证在多线程环境下也能正常运行。
Ⅴ 静态内部类实现
当 Singleton 类被加载时，静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载，
此时初始化 INSTANCE 实例，并且 JVM 能确保 INSTANCE 只被实例化一次。这种方式不仅具有延迟初始化的好处，而且由 JVM 提供了对线程安全的支持。
 */
public class SingletonDouble {

	private volatile static SingletonDouble instance;
	
	private SingletonDouble(){
		
	}
	
	public static SingletonDouble getInstance(){
		if (instance == null) {
			synchronized (SingletonDouble.class) {
				if (instance == null) {
					instance = new SingletonDouble();
				}
			}
		}
		return instance;
	}
}
